package server;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Locale;
import java.util.Scanner;
import java.util.Vector;
import java.util.concurrent.ConcurrentLinkedQueue;

public class Server
{
    private Vector<ServerThread> threads = new Vector<ServerThread>();
    private static float[][] grid;
    private float epsilon;
    private boolean solved = false;

    public static void main(String[] args) throws IOException
    {
        if (args.length != 2)
        {
            System.err.println("Usage: java Server <port number> <input file name>");
            System.exit(1);
        }
        int portNumber = Integer.parseInt(args[0]);
        String fileName = args[1];
        new Server(portNumber, fileName);
        System.out.println("Press any key to exit.");
        System.in.read();
    }

    public Server(int portNumber, String fileName)
    {
        this.prepareTask(fileName);
        this.useServerToGetSolution(portNumber);

        try
        {
            for (Thread thread : threads)
            {
                thread.join();
            }
            System.out.println("The resulting matrix is:\n");
            System.out.println(ServerThread.matrixToString(grid));
        }
        catch (InterruptedException interruptedException)
        {
            interruptedException.printStackTrace();
        }
    }

    public static synchronized void updateGrid(float [][] matrix, int upperBound, int bottomBound)
    {
        int columnsCount = grid[0].length;
        for (int i = upperBound + 1, k = 1; i < bottomBound; i++, k++)
        {
            for (int j = 1; j < columnsCount - 1; j++)
            {
                grid[i][j] = matrix[k][j];
            }
        }
    }

    public static float[][] getTaskData(int upperBound, int bottomBound)
    {
        int columnsCount = grid[0].length;
        int rowsCount = bottomBound - upperBound + 1;
        float [][] taskData = new float[rowsCount][columnsCount];
        int i, k;
        for (i = upperBound, k = 0; i <= bottomBound; i++, k++)
        {
            for (int j = 0; j < columnsCount; j++)
            {
                taskData[k][j] = grid[i][j];
            }
        }
        return taskData;
    }

    private void resetTasksBounds()
    {
        int rowsPerTask = (grid.length - 2) / threads.size();
        int rowsPerTaskLast = (grid.length - 2) - (threads.size() - 1) * rowsPerTask;
        for (int i = 0; i < threads.size(); i++)
        {
            ServerThread thread = threads.get(i);
            int upperBound = rowsPerTask * i;
            int addition = ((i == threads.size() - 1) && (rowsPerTaskLast != 0))
                    ? rowsPerTaskLast
                    : rowsPerTask;
            int bottomBound = upperBound + addition + 1;
            thread.setBounds(upperBound, bottomBound);
        }
    }

    private void useServerToGetSolution(int portNumber)
    {
        try
        (
            ServerSocket serverSocket = new ServerSocket(portNumber)
        )
        {
            serverSocket.setSoTimeout(1000);
            while (!isTaskSolved())
            {
                try
                {
                    Socket socket = serverSocket.accept();
                    if (threads.size() < grid.length - 2)
                    {
                        ServerThread serverThread = new ServerThread(socket, epsilon);
                        threads.add(serverThread);
                        resetTasksBounds();

                        serverThread.start();
                    }
                }
                catch (IOException ioException)
                {
                    //continue listening to port
                }
            }
        }
        catch (IOException e)
        {
            System.err.println("Could not listen on port " + portNumber);
            System.exit(-1);
        }
    }

    private boolean isTaskSolved()
    {
        boolean isSolved = threads.size() > 0;
        for (ServerThread thread : threads)
        {
            if (thread.isAlive())
            {
                isSolved = false;
            }
        }
        return isSolved;
    }

    private void prepareTask(String fileName)
    {
        try
        {
            Scanner inputScanner = new Scanner(new File(fileName));
            inputScanner.useLocale(Locale.ENGLISH);
            int rowsCount = inputScanner.nextInt();
            int columnsCount = inputScanner.nextInt();
            epsilon = inputScanner.nextFloat();
            grid = new float[rowsCount][columnsCount];

            /*
            *  Считвание значений границ сетки и заполнение нулями внутренних точек
            */
            for (int i = 0; i < rowsCount; i++)
            {
                for (int j = 0; j < columnsCount; j++)
                {
                    if ((i == 0) || (j == 0) || (i == rowsCount - 1) || (j == columnsCount - 1))
                    {
                        grid[i][j] = inputScanner.nextFloat();
                    }
                    else
                    {
                        grid[i][j] = 0;
                    }
                }
            }

        }
        catch (FileNotFoundException ex)
        {
            ex.printStackTrace();
        }
    }

}